package com.hujf.bp;

import java.io.IOException;
import java.util.Vector;


public class BPNeuralNetworks {

    static Double[][] sample =
            {
                    {0.0, 0.0, 0.0, 0.0},
                    {5.0, 1.0, 4.0, 19.020},
                    {5.0, 3.0, 3.0, 14.150},
                    {5.0, 5.0, 2.0, 14.360},
                    {5.0, 3.0, 3.0, 14.150},
                    {5.0, 3.0, 2.0, 15.390},
                    {5.0, 3.0, 2.0, 15.390},
                    {5.0, 5.0, 1.0, 19.680},
                    {5.0, 1.0, 2.0, 21.060},
                    {5.0, 3.0, 3.0, 14.150},
                    {5.0, 5.0, 4.0, 12.680},
                    {5.0, 5.0, 2.0, 14.360},
                    {5.0, 1.0, 3.0, 19.610},
                    {5.0, 3.0, 4.0, 13.650},
                    {5.0, 5.0, 5.0, 12.430},
                    {5.0, 1.0, 4.0, 19.020},
                    {5.0, 1.0, 4.0, 19.020},
                    {5.0, 3.0, 5.0, 13.390},
                    {5.0, 5.0, 4.0, 12.680},
                    {5.0, 1.0, 3.0, 19.610},
                    {5.0, 3.0, 2.0, 15.390},
//	    {1.0,3.0,1.0,11.110},
//	    {1.0,5.0,2.0,6.521},
//	    {1.0,1.0,3.0,10.190},
//	    {1.0,3.0,4.0,6.043},
//	    {1.0,5.0,5.0,5.242},
//	    {1.0,5.0,3.0,5.724},
//	    {1.0,1.0,4.0,9.766},
//	    {1.0,3.0,5.0,5.870},
//	    {1.0,5.0,4.0,5.406},
//	    {1.0,1.0,3.0,10.190},
//	    {1.0,1.0,5.0,9.545},
//	    {1.0,3.0,4.0,6.043},
//	    {1.0,5.0,3.0,5.724},
//	    {1.0,1.0,2.0,11.250},
//	    {1.0,3.0,1.0,11.110},
//	    {1.0,3.0,3.0,6.380},
//	    {1.0,5.0,2.0,6.521},
//	    {1.0,1.0,1.0,16.000},
//	    {1.0,3.0,2.0,7.219},
//	    {1.0,5.0,3.0,5.724}
            };

    public static void main(String[] args) throws IOException {

        Vector<Data> data = new Vector<Data>();
        System.out.println(sample.length);
        for (int i = 0; i < sample.length; i++) {
            Data t = new Data();
            for (int j = 0; j < 3; j++) {
                t.x.add(sample[i][j]);
            }
            t.y.add(sample[i][3]);
            data.add(t);
        }
        BPNN bp = new BPNN();
        bp.GetData(data);
        bp.Train();

        while (true) {
            Vector<Double> in = new Vector<Double>();
            for (int i = 0; i < 3; i++) {
                Double v = Double.valueOf(i);
                // in.add(5.0);
            }

            in.add(5.0);
            in.add(5.0);
            in.add(1.0);
            Vector<Double> ou;
            ou = bp.ForeCast(in);
            System.out.println(ou.get(0));
            break;
        }
    }

}

class Data {
    //lalala
    Vector<Double> x = new Vector<Double>();       //输入数据
    Vector<Double> y = new Vector<Double>();       //输出数据
};

class BPNN {
    //三层神经网络
    final int LAYER = 3;
    //每层的最多节点数
    final int NUM = 10;

    float A = (float) 30.0;
    float B = (float) 10.0;     //A和B是S型函数的参数
    int ITERS = 1000;     //最大训练次数
    float ETA_W = (float) 0.0035;   //权值调整率
    float ETA_B = (float) 0.001;    //阀值调整率
    float ERROR = (float) 0.002;    //单个样本允许的误差
    float ACCU = (float) 0.005;    //每次迭代允许的误差


    int in_num;                 //输入层节点数
    int ou_num;                 //输出层节点数
    int hd_num;                 //隐含层节点数

    Double[][][] w = new Double[LAYER][NUM][NUM];    //BP网络的权值
    Double[][] b = new Double[LAYER][NUM];         //BP网络节点的阀值

    Double[][] x = new Double[LAYER][NUM];         //每个神经元的值经S型函数转化后的输出值，输入层就为原值
    Double[][] d = new Double[LAYER][NUM];         //记录delta学习规则中delta的值


    Vector<Data> data;

    //获取训练所有样本数据
    void GetData(Vector<Data> _data) {
        data = _data;
    }

    //开始进行训练
    void Train() {
        System.out.printf("Begin to train BP NetWork!\n");
        GetNums();
        InitNetWork();
        int num = data.size();

        for (int iter = 0; iter <= ITERS; iter++) {
            for (int cnt = 0; cnt < num; cnt++) {
                //第一层输入节点赋值
                for (int i = 0; i < in_num; i++) {
                    x[0][i] = data.get(cnt).x.get(i);
                }

                while (true) {
                    ForwardTransfer();
                    if (GetError(cnt) < ERROR)    //如果误差比较小，则针对单个样本跳出循环
                    {
                        break;
                    }
                    ReverseTransfer(cnt);
                }
            }
            System.out.printf("This is the %d th trainning NetWork !", iter);

            Double accu = GetAccu();
            System.out.printf("  All Samples Accuracy is " + accu + "\n");
            if (accu < ACCU) {
                break;
            }
        }
        System.out.printf("The BP NetWork train End!\n");
    }

    //根据训练好的网络来预测输出值
    Vector<Double> ForeCast(Vector<Double> data) {
        int n = data.size();
        assert (n == in_num);
        for (int i = 0; i < in_num; i++) {
            x[0][i] = data.get(i);
        }

        ForwardTransfer();
        Vector<Double> v = new Vector<Double>();
        for (int i = 0; i < ou_num; i++) {
            v.add(x[2][i]);
        }
        return v;
    }

    //获取网络节点数
    void GetNums() {
        in_num = data.get(0).x.size();                         //获取输入层节点数
        ou_num = data.get(0).y.size();                         //获取输出层节点数
        hd_num = (int) Math.sqrt((in_num + ou_num) * 1.0) + 5;   //获取隐含层节点数
        if (hd_num > NUM) {
            hd_num = NUM;                     //隐含层数目不能超过最大设置
        }
    }

    //初始化网络
    void InitNetWork() {
        for (int i = 0; i < LAYER; i++) {
            for (int j = 0; j < NUM; j++) {
                for (int k = 0; k < NUM; k++) {
                    w[i][j][k] = 0.0;
                }
            }
        }
        for (int i = 0; i < LAYER; i++) {
            for (int j = 0; j < NUM; j++) {
                b[i][j] = 0.0;
            }
        }
    }

    //工作信号正向传递子过程
    void ForwardTransfer() {
        //计算隐含层各个节点的输出值
        for (int j = 0; j < hd_num; j++) {
            Double t = 0.0;
            for (int i = 0; i < in_num; i++) {
                t += w[1][i][j] * x[0][i];
            }
            t += b[1][j];
            x[1][j] = Sigmoid(t);
        }

        //计算输出层各节点的输出值
        for (int j = 0; j < ou_num; j++) {
            Double t = (double) 0;
            for (int i = 0; i < hd_num; i++) {
                t += w[2][i][j] * x[1][i];
            }
            t += b[2][j];
            x[2][j] = Sigmoid(t);
        }
    }

    //计算单个样本的误差
    double GetError(int cnt) {
        Double ans = (double) 0;
        for (int i = 0; i < ou_num; i++) {
            ans += 0.5 * (x[2][i] - data.get(cnt).y.get(i)) * (x[2][i] - data.get(cnt).y.get(i));
        }
        return ans;
    }

    //误差信号反向传递子过程
    void ReverseTransfer(int cnt) {
        CalcDelta(cnt);
        UpdateNetWork();
    }

    //计算所有样本的精度
    double GetAccu() {
        Double ans = (double) 0;
        int num = data.size();
        for (int i = 0; i < num; i++) {
            int m = data.get(i).x.size();
            for (int j = 0; j < m; j++) {
                x[0][j] = data.get(i).x.get(j);
            }
            ForwardTransfer();
            int n = data.get(i).y.size();
            for (int j = 0; j < n; j++) {
                ans += 0.5 * (x[2][j] - data.get(i).y.get(j)) * (x[2][j] - data.get(i).y.get(j));
            }
        }
        return ans / num;
    }

    //计算调整量
    void CalcDelta(int cnt) {
        //计算输出层的delta值
        for (int i = 0; i < ou_num; i++) {
            d[2][i] = (x[2][i] - data.get(cnt).y.get(i)) * x[2][i] * (A - x[2][i]) / (A * B);
        }
        //计算隐含层的delta值
        for (int i = 0; i < hd_num; i++) {
            Double t = (double) 0;
            for (int j = 0; j < ou_num; j++) {
                t += w[2][i][j] * d[2][j];
            }
            d[1][i] = t * x[1][i] * (A - x[1][i]) / (A * B);
        }
    }

    //根据计算出的调整量对BP网络进行调整
    void UpdateNetWork() {
        //隐含层和输出层之间权值和阀值调整
        for (int i = 0; i < hd_num; i++) {
            for (int j = 0; j < ou_num; j++) {
                w[2][i][j] -= ETA_W * d[2][j] * x[1][i];
            }
        }
        for (int i = 0; i < ou_num; i++) {
            b[2][i] -= ETA_B * d[2][i];
        }

        //输入层和隐含层之间权值和阀值调整
        for (int i = 0; i < in_num; i++) {
            for (int j = 0; j < hd_num; j++) {
                w[1][i][j] -= ETA_W * d[1][j] * x[0][i];
            }
        }
        for (int i = 0; i < hd_num; i++) {
            b[1][i] -= ETA_B * d[1][i];
        }
    }

    //计算Sigmoid函数的值
    Double Sigmoid(Double x) {
        return A / (1 + Math.exp(-x / B));
    }

}



